import { $set } from '@feng3d/serialization';
import { BufferGeometry3D, Color, ExtrudeGeometry3D, LineBasicMaterial3D, MeshPhongMaterial3D, Node3D, Path, PerspectiveCamera3D, PointsMaterial3D, Scene3D, Shape, ShapeGeometry3D, StatsComponent, TextureLoader3D, Vector2, WebGLRenderer3D } from 'feng3d-three';

const rootPath = '../../';

let container: HTMLDivElement;
let stats: StatsComponent;

let camera: PerspectiveCamera3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;

let group: Node3D;

let targetRotation = 0;
let targetRotationOnPointerDown = 0;

let pointerX = 0;
let pointerXOnPointerDown = 0;

let windowHalfX = window.innerWidth / 2;

init();
animate();

function init()
{
    container = document.createElement('div');
    document.body.appendChild(container);

    scene = new Node3D().addComponent('Scene3D');
    scene.background = new Color(0xf0f0f0);

    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 50, aspect: window.innerWidth / window.innerHeight, near: 1, far: 1000 });
    camera.entity.position.set(0, 150, 500);
    scene.entity.addChild(camera.entity);

    const light = new Node3D().addComponent('PointLight3D', { color: { value: 0xffffff }, intensity: 0.8 });
    camera.entity.addChild(light.entity);

    group = new Node3D();
    group.position.y = 50;
    scene.entity.addChild(group);

    const loader = new TextureLoader3D();
    const texture = loader.load(`${rootPath}textures/uv_grid_opengl.jpg`);

    // it's necessary to apply these settings in order to correctly display the texture on a shape geometry

    texture.wrapS = texture.wrapT = 'RepeatWrapping';
    texture.repeat.set(0.008, 0.008);

    function addShape(shape, extrudeSettings: any, color: number, x: number, y: number, z: number, rx: number, ry: number, rz: number, s: number)
    {
        // flat shape with texture
        // note: default UVs generated by THREE.ShapeGeometry are simply the x- and y-coordinates of the vertices

        let geometry: BufferGeometry3D = $set(new ShapeGeometry3D(), { shapes: shape });

        let mesh = new Node3D().addComponent('Mesh3D', {
            geometry, material: $set(new MeshPhongMaterial3D(), { side: 'DoubleSide', map: texture })
        });
        mesh.entity.position.set(x, y, z - 175);
        mesh.entity.rotation.set(rx, ry, rz);
        mesh.entity.scale.set(s, s, s);
        group.addChild(mesh.entity);

        // flat shape

        geometry = $set(new ShapeGeometry3D(), { shapes: shape });

        mesh = new Node3D().addComponent('Mesh3D', { geometry, material: $set(new MeshPhongMaterial3D(), { color: { value: color }, side: 'DoubleSide' }) });
        mesh.entity.position.set(x, y, z - 125);
        mesh.entity.rotation.set(rx, ry, rz);
        mesh.entity.scale.set(s, s, s);
        group.addChild(mesh.entity);

        // extruded shape

        geometry = $set(new ExtrudeGeometry3D(), { shapes: shape, options: extrudeSettings });

        mesh = new Node3D().addComponent('Mesh3D', { geometry, material: $set(new MeshPhongMaterial3D(), { color: { value: color } }) });
        mesh.entity.position.set(x, y, z - 75);
        mesh.entity.rotation.set(rx, ry, rz);
        mesh.entity.scale.set(s, s, s);
        group.addChild(mesh.entity);

        addLineShape(shape, color, x, y, z, rx, ry, rz, s);
    }

    function addLineShape(shape, color, x, y, z, rx, ry, rz, s)
    {
        // lines

        shape.autoClose = true;

        const points = shape.getPoints();
        const spacedPoints = shape.getSpacedPoints(50);

        const geometryPoints = new BufferGeometry3D().setFromPoints(points);
        const geometrySpacedPoints = new BufferGeometry3D().setFromPoints(spacedPoints);

        // solid line

        let line = new Node3D().addComponent('Line3D', { geometry: geometryPoints, material: new LineBasicMaterial3D({ color }) });
        line.entity.position.set(x, y, z - 25);
        line.entity.rotation.set(rx, ry, rz);
        line.entity.scale.set(s, s, s);
        group.addChild(line.entity);

        // line from equidistance sampled points

        line = new Node3D().addComponent('Line3D', { geometry: geometrySpacedPoints, material: new LineBasicMaterial3D({ color }) });
        line.entity.position.set(x, y, z + 25);
        line.entity.rotation.set(rx, ry, rz);
        line.entity.scale.set(s, s, s);
        group.addChild(line.entity);

        // vertices from real points

        let particles = new Node3D().addComponent('Points3D', { geometry: geometryPoints, material: new PointsMaterial3D({ color, size: 4 }) });
        particles.entity.position.set(x, y, z + 75);
        particles.entity.rotation.set(rx, ry, rz);
        particles.entity.scale.set(s, s, s);
        group.addChild(particles.entity);

        // equidistance sampled points

        particles = new Node3D().addComponent('Points3D', { geometry: geometrySpacedPoints, material: new PointsMaterial3D({ color, size: 4 }) });
        particles.entity.position.set(x, y, z + 125);
        particles.entity.rotation.set(rx, ry, rz);
        particles.entity.scale.set(s, s, s);
        group.addChild(particles.entity);
    }

    // California

    const californiaPts: Vector2[] = [];

    californiaPts.push(new Vector2(610, 320));
    californiaPts.push(new Vector2(450, 300));
    californiaPts.push(new Vector2(392, 392));
    californiaPts.push(new Vector2(266, 438));
    californiaPts.push(new Vector2(190, 570));
    californiaPts.push(new Vector2(190, 600));
    californiaPts.push(new Vector2(160, 620));
    californiaPts.push(new Vector2(160, 650));
    californiaPts.push(new Vector2(180, 640));
    californiaPts.push(new Vector2(165, 680));
    californiaPts.push(new Vector2(150, 670));
    californiaPts.push(new Vector2(90, 737));
    californiaPts.push(new Vector2(80, 795));
    californiaPts.push(new Vector2(50, 835));
    californiaPts.push(new Vector2(64, 870));
    californiaPts.push(new Vector2(60, 945));
    californiaPts.push(new Vector2(300, 945));
    californiaPts.push(new Vector2(300, 743));
    californiaPts.push(new Vector2(600, 473));
    californiaPts.push(new Vector2(626, 425));
    californiaPts.push(new Vector2(600, 370));
    californiaPts.push(new Vector2(610, 320));

    for (let i = 0; i < californiaPts.length; i++) californiaPts[i].multiplyScalar(0.25);

    const californiaShape = new Shape(californiaPts);

    // Triangle

    const triangleShape = new Shape()
        .moveTo(80, 20)
        .lineTo(40, 80)
        .lineTo(120, 80)
        .lineTo(80, 20); // close path

    // Heart

    const x = 0;
    const y = 0;

    const heartShape = new Shape()
        .moveTo(x + 25, y + 25)
        .bezierCurveTo(x + 25, y + 25, x + 20, y, x, y)
        .bezierCurveTo(x - 30, y, x - 30, y + 35, x - 30, y + 35)
        .bezierCurveTo(x - 30, y + 55, x - 10, y + 77, x + 25, y + 95)
        .bezierCurveTo(x + 60, y + 77, x + 80, y + 55, x + 80, y + 35)
        .bezierCurveTo(x + 80, y + 35, x + 80, y, x + 50, y)
        .bezierCurveTo(x + 35, y, x + 25, y + 25, x + 25, y + 25);

    // Square

    const sqLength = 80;

    const squareShape = new Shape()
        .moveTo(0, 0)
        .lineTo(0, sqLength)
        .lineTo(sqLength, sqLength)
        .lineTo(sqLength, 0)
        .lineTo(0, 0);

    // Rounded rectangle

    const roundedRectShape = new Shape();

    (function roundedRect(ctx, x, y, width, height, radius)
    {
        ctx.moveTo(x, y + radius);
        ctx.lineTo(x, y + height - radius);
        ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
        ctx.lineTo(x + width - radius, y + height);
        ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
        ctx.lineTo(x + width, y + radius);
        ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
        ctx.lineTo(x + radius, y);
        ctx.quadraticCurveTo(x, y, x, y + radius);
    })(roundedRectShape, 0, 0, 50, 50, 20);

    // Track

    const trackShape = new Shape()
        .moveTo(40, 40)
        .lineTo(40, 160)
        .absarc(60, 160, 20, Math.PI, 0, true)
        .lineTo(80, 40)
        .absarc(60, 40, 20, 2 * Math.PI, Math.PI, true);

    // Circle

    const circleRadius = 40;
    const circleShape = new Shape()
        .moveTo(0, circleRadius)
        .quadraticCurveTo(circleRadius, circleRadius, circleRadius, 0)
        .quadraticCurveTo(circleRadius, -circleRadius, 0, -circleRadius)
        .quadraticCurveTo(-circleRadius, -circleRadius, -circleRadius, 0)
        .quadraticCurveTo(-circleRadius, circleRadius, 0, circleRadius);

    // Fish

    const fishShape = new Shape()
        .moveTo(x, y)
        .quadraticCurveTo(x + 50, y - 80, x + 90, y - 10)
        .quadraticCurveTo(x + 100, y - 10, x + 115, y - 40)
        .quadraticCurveTo(x + 115, y, x + 115, y + 40)
        .quadraticCurveTo(x + 100, y + 10, x + 90, y + 10)
        .quadraticCurveTo(x + 50, y + 80, x, y);

    // Arc circle

    const arcShape = new Shape()
        .moveTo(50, 10)
        .absarc(10, 10, 40, 0, Math.PI * 2, false);

    const holePath = new Path()
        .moveTo(20, 10)
        .absarc(10, 10, 10, 0, Math.PI * 2, true);

    arcShape.holes.push(holePath);

    // Smiley

    const smileyShape = new Shape()
        .moveTo(80, 40)
        .absarc(40, 40, 40, 0, Math.PI * 2, false);

    const smileyEye1Path = new Path()
        .moveTo(35, 20)
        .absellipse(25, 20, 10, 10, 0, Math.PI * 2, true, undefined);

    const smileyEye2Path = new Path()
        .moveTo(65, 20)
        .absarc(55, 20, 10, 0, Math.PI * 2, true);

    const smileyMouthPath = new Path()
        .moveTo(20, 40)
        .quadraticCurveTo(40, 60, 60, 40)
        .bezierCurveTo(70, 45, 70, 50, 60, 60)
        .quadraticCurveTo(40, 80, 20, 60)
        .quadraticCurveTo(5, 50, 20, 40);

    smileyShape.holes.push(smileyEye1Path);
    smileyShape.holes.push(smileyEye2Path);
    smileyShape.holes.push(smileyMouthPath);

    // Spline shape

    const splinepts: Vector2[] = [];
    splinepts.push(new Vector2(70, 20));
    splinepts.push(new Vector2(80, 90));
    splinepts.push(new Vector2(-30, 70));
    splinepts.push(new Vector2(0, 0));

    const splineShape = new Shape()
        .moveTo(0, 0)
        .splineThru(splinepts);

    const extrudeSettings = { depth: 8, bevelEnabled: true, bevelSegments: 2, steps: 2, bevelSize: 1, bevelThickness: 1 };

    // addShape( shape, color, x, y, z, rx, ry,rz, s );

    addShape(californiaShape, extrudeSettings, 0xf08000, -300, -100, 0, 0, 0, 0, 1);
    addShape(triangleShape, extrudeSettings, 0x8080f0, -180, 0, 0, 0, 0, 0, 1);
    addShape(roundedRectShape, extrudeSettings, 0x008000, -150, 150, 0, 0, 0, 0, 1);
    addShape(trackShape, extrudeSettings, 0x008080, 200, -100, 0, 0, 0, 0, 1);
    addShape(squareShape, extrudeSettings, 0x0040f0, 150, 100, 0, 0, 0, 0, 1);
    addShape(heartShape, extrudeSettings, 0xf00000, 60, 100, 0, 0, 0, Math.PI, 1);
    addShape(circleShape, extrudeSettings, 0x00f000, 120, 250, 0, 0, 0, 0, 1);
    addShape(fishShape, extrudeSettings, 0x404040, -60, 200, 0, 0, 0, 0, 1);
    addShape(smileyShape, extrudeSettings, 0xf000f0, -200, 250, 0, 0, 0, Math.PI, 1);
    addShape(arcShape, extrudeSettings, 0x804000, 150, 0, 0, 0, 0, 0, 1);
    addShape(splineShape, extrudeSettings, 0x808080, -50, -100, 0, 0, 0, 0, 1);

    addLineShape(arcShape.holes[0], 0x804000, 150, 0, 0, 0, 0, 0, 1);

    for (let i = 0; i < smileyShape.holes.length; i += 1)
    {
        addLineShape(smileyShape.holes[i], 0xf000f0, -200, 250, 0, 0, 0, Math.PI, 1);
    }

    //

    renderer = scene.addComponent('WebGLRenderer3D', { parameters: { antialias: true } });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);

    stats = scene.addComponent('StatsComponent');
    container.appendChild(stats.dom);

    container.style.touchAction = 'none';
    container.addEventListener('pointerdown', onPointerDown);

    //

    window.addEventListener('resize', onWindowResize);
}

function onWindowResize()
{
    windowHalfX = window.innerWidth / 2;

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

//

function onPointerDown(event: PointerEvent)
{
    if (event.isPrimary === false) return;

    pointerXOnPointerDown = event.clientX - windowHalfX;
    targetRotationOnPointerDown = targetRotation;

    document.addEventListener('pointermove', onPointerMove);
    document.addEventListener('pointerup', onPointerUp);
}

function onPointerMove(event)
{
    if (event.isPrimary === false) return;

    pointerX = event.clientX - windowHalfX;

    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
}

function onPointerUp(event: PointerEvent)
{
    if (event.isPrimary === false) return;

    document.removeEventListener('pointermove', onPointerMove);
    document.removeEventListener('pointerup', onPointerUp);
}

//

function animate()
{
    requestAnimationFrame(animate);

    render();
    stats.update();
}

function render()
{
    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
    renderer.render(scene, camera);
}
